//   Copyright 2012,2013 Vaughn Vernon
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package com.saasovation.common.domain.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @领域事件发布者
 */
public class DomainEventPublisher {

    /**
     * 使用线程局部变量的方式，来创建@领域事件发布者的静态的最终的单例
     */
    private static final ThreadLocal<DomainEventPublisher> instance = ThreadLocal.withInitial(() -> new DomainEventPublisher());

    /**
     * 局部变量：发布中
     */
    private boolean publishing;

    /**
     * 订阅者清单
     */
    private List subscribers;

    /**
     * 静态方法：获取领域事件发布者本身的一个静态最终实例
     * @return
     */
    public static DomainEventPublisher instance() {
        return instance.get();
    }

    /**
     * 发布（领域事件）
     * @param aDomainEvent 领域事件
     * @param <T> 泛型类
     */
    public <T> void publish(final T aDomainEvent) {
        // 发布的条件：发布中以外 且 订阅者清单有
        if (!this.isPublishing() && this.hasSubscribers()) {

            try {
                // 首先，设置状态：发布中
                this.setPublishing(true);

                // 获取领域事件的类型
                Class<?> eventType = aDomainEvent.getClass();

                // 获取领域事件订阅者清单
                @SuppressWarnings("unchecked")
                List<DomainEventSubscriber<T>> allSubscribers = this.subscribers();

                // 遍历清单中的所有领域事件订阅者，依次处理
                for (DomainEventSubscriber<T> subscriber : allSubscribers) {
                    // 获取每个订阅者的事件类型
                    Class<?> subscribedToType = subscriber.subscribedToEventType();
                    // 处理事件的条件；如果参数的领域事件类型等于 订阅者的事件类型 或者 订阅者的事件类型 等于 领域事件类
                    if (eventType == subscribedToType || subscribedToType == DomainEvent.class) {
                        // 领域事件订阅者 处理其相应的领域事件
                        subscriber.handleEvent(aDomainEvent);
                    }
                }

            } finally {
                // 处理完毕，更新发布中状态为假：意为发布完毕
                this.setPublishing(false);
            }
        }
    }

    /**
     * 发布所有领域事件
     * @param aDomainEvents 领域事件清单
     */
    public void publishAll(Collection<DomainEvent> aDomainEvents) {
        // 遍历清单的每个领域事件，依次进行发布
        for (DomainEvent domainEvent : aDomainEvents) {
            this.publish(domainEvent);
        }
    }

    /**
     * 重置订阅者清单
     */
    public void reset() {
        // 重置的条件是 当发布者不是在发布中的情况下，设置订阅者清单为无效
        if (!this.isPublishing()) {
            this.setSubscribers(null);
        }
    }

    /**
     * 订阅（由领域事件发布者发布的东西）
     * @param aSubscriber 领域事件订阅者
     * @param <T> 泛型类
     */
    @SuppressWarnings("unchecked")
    public <T> void subscribe(DomainEventSubscriber<T> aSubscriber) {
        // 订阅的条件是：当发布者不是在发布中的情况下
        if (!this.isPublishing()) {
            // 确保订阅者清单有效
            this.ensureSubscribersList();
            // 追加领域事件订阅者到最新的订阅者清单
            this.subscribers().add(aSubscriber);
        }
    }

    /**
     * 私有的默认领域事件发布者构造器
     */
    private DomainEventPublisher() {
        // 调用父类默认构造器
        super();

        // 设置发布中状态为假（未发布状态）
        this.setPublishing(false);
        // 确保领域事件订阅者清单有效
        this.ensureSubscribersList();
    }

    /**
     * 确保订阅者集合有效
     */
    @SuppressWarnings("rawtypes")
    private void ensureSubscribersList() {
        // 如果集合里没有任何订阅者，也要初始有效化
        if (!this.hasSubscribers()) {
            this.setSubscribers(new ArrayList());
        }
    }

    /**
     * 是否在发布中
     * @return 返回布尔型
     */
    private boolean isPublishing() {
        return this.publishing;
    }

    /**
     * 更新发布状态
     * @param aFlag 最新发布状态
     */
    private void setPublishing(boolean aFlag) {
        this.publishing = aFlag;
    }

    /**
     * 是否有订阅者清单
     * @return 返回布尔
     */
    private boolean hasSubscribers() {
        // 订阅者集合无效的话，返回有订阅者
        return this.subscribers() != null;
    }

    /**
     * 获取订阅者清单
     * @return 返回列表
     */
    @SuppressWarnings("rawtypes")
    private List subscribers() {
        return this.subscribers;
    }

    /**
     * 设置订阅者集合
     * @param aSubscriberList 订阅者集合
     */
    @SuppressWarnings("rawtypes")
    private void setSubscribers(List aSubscriberList) {
        this.subscribers = aSubscriberList;
    }
}

